package cn.autumnorange.app.user.consumer.authorization.security.security;

import cn.autumnorange.app.user.consumer.authorization.security.properties.OAuth2ClientProperties;
import cn.autumnorange.app.user.consumer.authorization.security.properties.OAuth2Properties;
import org.apache.commons.lang.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.builders.ClientDetailsServiceBuilder;
import org.springframework.security.oauth2.config.annotation.builders.InMemoryClientDetailsServiceBuilder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import java.util.ArrayList;
import java.util.List;

/**
 * Created on 2018/1/15 0015.
 *
 * @author zlf
 * @email i@merryyou.cn @update wsf
 * @since 1.0
 */
@Configuration
@EnableAuthorizationServer
public class MerryyouAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
  @Autowired private OAuth2Properties oAuth2Properties;
  /** 注入上下文认证管理器 */
  @Autowired private AuthenticationManager authenticationManager;

  @Autowired private UserDetailsService userDetailsServiceImpl;

  /** 详见TokenStoreConfig类 */
  @Autowired private TokenStore myRedisTokenStore;

  /** Jwt访问令牌转换器 */
  @Autowired(required = false)
  private JwtAccessTokenConverter jwtAccessTokenConverter;

  /** 令牌增强器 扩展jwttoken存储信息 */
  @Autowired(required = false)
  private TokenEnhancer jwtTokenEnhancer;

  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

  @Autowired private PasswordEncoder passwordEncoder;

  @Autowired private RedisConnectionFactory redisConnectionFactory;

  @Bean
  public AuthorizationCodeServices authorizationCodeServices() {
    return new RedisAuthorizationCodeServiceImpl(redisConnectionFactory);
  }

  @Autowired private AuthorizationCodeServices redisAuthenticationCodeServices;
  /**
   * 定义授权和令牌端点以及令牌服务
   *
   * @param endpoints defines the authorization and token endpoints and the token services.
   * @throws Exception
   */
  @Override
  public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

    endpoints
        .authenticationManager(authenticationManager)
        .tokenStore(myRedisTokenStore)
        // 用户账号密码认证
        .userDetailsService(userDetailsServiceImpl)
        // 开启刷新token;
        .reuseRefreshTokens(true)
        //      自定义tokenServices 解决单点登录以及 ?sso
        .tokenServices(tokenServices(endpoints));
    // 扩展token返回结果
    if (jwtAccessTokenConverter != null && jwtTokenEnhancer != null) {
      TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
      List<TokenEnhancer> enhancerList = new ArrayList();
      enhancerList.add(jwtTokenEnhancer);
      enhancerList.add(jwtAccessTokenConverter);
      tokenEnhancerChain.setTokenEnhancers(enhancerList);
      // jwt
      endpoints.tokenEnhancer(tokenEnhancerChain).accessTokenConverter(jwtAccessTokenConverter);
      endpoints.authorizationCodeServices(redisAuthenticationCodeServices);

      //      endpoints.tokenServices(defaultTokenServices());

      //            endpoints.exceptionTranslator();
    }
  }

  /**
   * 配置客户端Client的一些信息 用于定义客户端详细信息服务的配置程序。可以初始化客户端详细信息，
   *
   * @param clients a configurer that defines the client details service. Client details can be
   *     initialized, or you can just refer to an existing store.
   * @throws Exception
   */
  @Override
  public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    InMemoryClientDetailsServiceBuilder build = clients.inMemory();
    if (ArrayUtils.isNotEmpty(oAuth2Properties.getClients())) {
      for (OAuth2ClientProperties config : oAuth2Properties.getClients()) {
        ClientDetailsServiceBuilder.ClientBuilder clientBuilder =
            build.withClient(config.getClientId());
        clientBuilder
            .secret(passwordEncoder.encode(config.getClientSecret()))
            // 设置token有效期
            .accessTokenValiditySeconds(config.getAccessTokenValiditySeconds())
            // 设置refreshToken有效期
            .refreshTokenValiditySeconds(config.getRefreshTokenValiditySeconds())
            //                 OAuth2支持的认证方式
            .authorizedGrantTypes(config.getAuthorizedGrantTypes())
            .redirectUris(config.getRedirectUris())
            //                .scopes(SCOPE_READ,SCOPE_WRITE,TRUST)
            // 允许授权范围 授权域
            .scopes(config.getScopes())
            //        设置自动授权为true 则不会被重定向到授权的页面，也不需要手动给请求授权,直接自动授权成功返回code
            .autoApprove(true);
      }
    }
  }

  //    @Override
  //    public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
  //        //允许表单认证
  //        oauthServer.allowFormAuthenticationForClients();
  //        oauthServer.passwordEncoder(passwordEncoder);
  //    }

  /**
   * 定义令牌端点上的安全性约 束
   *
   * @param security oauthServer defines the security constraints on the token endpoint.
   * @throws Exception exception springSecurity 授权表达式
   */
  @Override
  public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {

    //      security.authenticationEntryPoint(jsonBasicAuthenticationEntryPoint);
    //      允许所有人请求令牌
    security.tokenKeyAccess("permitAll()");
    //    已验证的客户端才能请求check_token端点
    security.checkTokenAccess("isAuthenticated()");
    //    允许资源客户端的form表单认证
    security.allowFormAuthenticationForClients();
  }

  //  /**
  //   * <p>注意，自定义TokenServices的时候，需要设置@Primary，否则报错，</p>
  //   *
  //   * @return
  //   */
  //  @Primary
  //  @Bean
  //  public DefaultTokenServices defaultTokenServices() {
  //    DefaultTokenServices tokenServices = new DefaultTokenServices();
  //    tokenServices.setTokenStore(tokenStore);
  //    tokenServices.setSupportRefreshToken(true);
  //    tokenServices.setClientDetailsService(clientDetails());
  //    tokenServices.setAccessTokenValiditySeconds(60 * 60 * 12); // token有效期自定义设置，默认12小时
  //    tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);//默认30天，这里修改
  //    return tokenServices;
  //  }

  private SsoTokenServices tokenServices(AuthorizationServerEndpointsConfigurer endpoints) {
    // 自定义sso单点登录TokenServices
    SsoTokenServices tokenServices = new SsoTokenServices();
    tokenServices.setTokenStore(myRedisTokenStore);
    tokenServices.setSupportRefreshToken(true);
    // 支持刷新token
    tokenServices.setReuseRefreshToken(true);
    tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
    tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
    // token有效期自定义设置单位秒，默认12小时
    tokenServices.setAccessTokenValiditySeconds(20);
    // token刷新时间 默认30天
    tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);
    // addUserDetailsService 只是生成AuthenticationManager 并设进tokenServices中
    //        addUserDetailsService(tokenServices, this.userDetailsServiceImpl);
    tokenServices.setAuthenticationManager(authenticationManager);
    return tokenServices;
  }

  private void addUserDetailsService(
      SsoTokenServices tokenServices, UserDetailsService userDetailsService) {
    //      为什么要重新new AuthenticationManager？
    //        if (userDetailsService != null) {
    //            PreAuthenticatedAuthenticationProvider provider = new
    // PreAuthenticatedAuthenticationProvider();
    //            provider.setPreAuthenticatedUserDetailsService(new
    // UserDetailsByNameServiceWrapper<>(
    //                    userDetailsService));
    //            tokenServices.setAuthenticationManager(new
    // ProviderManager(java.util.Arrays.asList(provider)));
    //        }
    tokenServices.setAuthenticationManager(authenticationManager);
  }
  //  @Bean
  //  public ClientDetailsService clientDetails() {
  //    return new JdbcClientDetailsService(dataSource);
  //  }
}
